﻿using System;
using System.ComponentModel;
using System.Runtime.InteropServices;

using System.Diagnostics;

namespace Nextensions.WinApi.Input
{
    public class MouseHook: HookBase
    {
        public delegate void MouseEventHandler(MouseEvent eventType, uint timestamp, short x, short y, short wheelDelta);
        public event MouseEventHandler MouseEventOccured;

        private long _doubleClickTimeAsTicks; // There are 10,000 ticks per millisecond. We'll translate to ticks when we startup so we don't have to keep making the transition between ticks and milliseconds.
        private long _leftButtonLastDown = 0;

        public MouseHook(): base()
        {
            _doubleClickTimeAsTicks = Win.GetDoubleClickTime() * 10000; // There are 10,000 ticks per millisecond
            Debug.WriteLine("system double click time is (in ticks): {0}", _doubleClickTimeAsTicks);
        }

        override public bool Start()
        {
            _hookProc = new Win.HookProc(MouseHookLowLevel);
            _hHook = Win.SetWindowsHookEx(HookType.WH_MOUSE_LL, _hookProc, (IntPtr)0, 0);

            return _hHook.ToInt32() != 0;
        }

        private int MouseHookLowLevel(int nCode, IntPtr wParam, IntPtr lParam)
        {
            MSLLHOOKSTRUCT ll = (MSLLHOOKSTRUCT)Marshal.PtrToStructure(lParam, typeof(MSLLHOOKSTRUCT));

            if (nCode < 0)
            {
                return Win.CallNextHookEx(_hHook, nCode, wParam, lParam);
            }
            else
            {
                //System.Diagnostics.Debug.WriteLine("Mouse event: " + wParam.ToInt32());
                MouseEvent eventType = MouseEvent.None;
                //int paramAsInt = wParam.ToInt32();

                //System.Diagnostics.Debug.WriteLine("LL mouse event: " + paramAsInt);

                //if(!Enum.IsDefined(typeof(WM), paramAsInt))
                //{
                //    System.Diagnostics.Debug.WriteLine("Value is not defined in enum");
               //     return CallNextHook ? 0 : 1;
               // }

                //System.Diagnostics.Debug.WriteLine("Param as int: " + paramAsInt);

                short wheelDelta = 0;
                //WM enumMember = WM.ACTIVATE;
                //try
                //{
               //     enumMember = (WM)paramAsInt;
               // }
               // catch (Exception ex)
               // {
               //     System.Diagnostics.Debug.WriteLine(ex.Message);
              //  }

                //System.Diagnostics.Debug.WriteLine("Converted to enum member: " + enumMember);

                switch (wParam.ToInt32())
                {
                    case (int)WM.MOUSEMOVE:

                        eventType = MouseEvent.Move;
                        break;

                    case (int)WM.LBUTTONDOWN:

                        Debug.WriteLine("click time diff: {0}", DateTime.UtcNow.Ticks - _leftButtonLastDown);

                        eventType = DateTime.UtcNow.Ticks - _leftButtonLastDown < _doubleClickTimeAsTicks ?
                            MouseEvent.LDoubleClick :
                            eventType = MouseEvent.LDown;

                        _leftButtonLastDown = DateTime.UtcNow.Ticks;
                        Debug.WriteLine("set last button down time: {0}", _leftButtonLastDown);

                        break;
                    
                    case (int)WM.LBUTTONUP:

                        eventType = MouseEvent.LUp;

                        break;

                    case (int)WM.RBUTTONDOWN:

                        eventType = MouseEvent.RDown;
                        break;

                    case (int)WM.RBUTTONUP:

                        eventType = MouseEvent.RUp;
                        break;

                    case (int)WM.MOUSEWHEEL:

                        eventType = MouseEvent.Wheel;
                        wheelDelta = (short)(ll.mouseData >> 16);
                        break;
                }

                if(eventType != MouseEvent.None)
                    MouseEventOccured(eventType, ll.time, (short)ll.pt.X, (short)ll.pt.Y, wheelDelta);

                // Return non-zero to prevent actions from reaching next procedure
                if (!CallNextHook)
                    return 1;

                return Win.CallNextHookEx(_hHook, nCode, wParam, lParam);
            }
        }
    }
}
